package org.raymond.iworks.study.netty.atguigu.nio;

import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Arrays;

@Slf4j
public class BasicChannel {
    public static void main(String[] args) throws Exception {
        gatheringAndScatteringByteTest();
    }

    public static void copy() throws Exception {
        File from = new File("tmp/1.txt");
        File to = new File("tmp/2.txt");
        FileInputStream fis = new FileInputStream(from);
        FileOutputStream tos = new FileOutputStream(to);
        // 从流获取通道
        FileChannel fisChannel = fis.getChannel();
        FileChannel tosChannel = tos.getChannel();
        // 创建一个bytebuffer, 小文件直接一次读完
        ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        while(true){
            // 每次都要清空,否则write之后buffer中的limit与position相等,read只能一直读到0
            // 陷入死循环
            byteBuffer.clear();
            int read = fisChannel.read(byteBuffer);
            if(read>=0) {
                byteBuffer.flip();
                tosChannel.write(byteBuffer);
            }else{
                break;
            }
        }

        fis.close();
        tos.close();
    }

    /**
     * 1, MappedByteBuffer可以让文件直接在内存中(java堆外内存)修改,不需要操作系统先拷贝一次
     */
    public static void mappedByteChannel() throws Exception {
        RandomAccessFile randomAccessFile = new RandomAccessFile("tmp/1.txt", "rw");
        FileChannel channel = randomAccessFile.getChannel();
        /**
         * 将通道关联的文件映射一部分到内存中
         * mode:文件映射到内存的模式
         * position:文件映射到内存的起始位置
         * size:文件映射到内存的大小,即可以直接修改的范围(0-4:0,1,2,3,4)
         * map后得到的实际类型是DirectByteBuffer
         */
        MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
        buffer.clear();
        buffer.put(1, (byte)'H');
        buffer.put(4, (byte)'D');
        // buffer.put(5, (byte)9); // java.lang.IndexOutOfBoundsException
        randomAccessFile.close(); // 修改成功,需要在外部用别的应用打开
    }

    /**
     * Scattering: 将数据从channel写入到buffer时,可以采用数组依次写入(分散)
     * Gathering: 从buffer读取数据时,可以采用数组依次读出到channel(聚合)
     * @throws Exception
     */
    public static void gatheringAndScatteringByteTest() throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);
        serverSocketChannel.socket().bind(inetSocketAddress);
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);
        SocketChannel sc = serverSocketChannel.accept();
        // bytebuffer一般是非阻塞的,无法知道是否已读完.
        // 循环读取,因为不知道客户端会发多少字节过来
        while(true){
            long readall = 0;
            while(true){
                long read = sc.read(byteBuffers);
                if(read>0) {
                    log.info("读{}个字节", read);
                    readall += read;
                    Arrays.asList(byteBuffers).stream().map(buffer -> "read position=" + buffer.position() + ",limit=" + buffer.limit()).forEach(System.out::println);
                }else{
                    log.info("未读到数据");
                    break;
                }
            }

            Arrays.asList(byteBuffers).forEach(ByteBuffer::flip);

            // 回显给客户端
            long writeall = 0;
            while(true){
                long write=sc.write(byteBuffers);
                if(write>0) {
                    log.info("写入{}个字节", write);
                    writeall += write;
                    Arrays.asList(byteBuffers).stream().map(buffer -> "write position=" + buffer.position() + ",limit=" + buffer.limit()).forEach(System.out::println);
                }else{
                    log.info("未写入数据");
                    break;
                }
            }

            Arrays.asList(byteBuffers).forEach(ByteBuffer::compact);
            log.info("read:{}, write:{}", readall, writeall);
            if(readall==0 && writeall==0){
                break;
            }
        }
    }

    public static void testWrite() throws IOException {
        String s = "hello nio";
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(s.getBytes(StandardCharsets.UTF_8));
        byteBuffer.flip();

        try(FileChannel c = FileChannel.open(Path.of("tmp/1.txt"), StandardOpenOption.WRITE)){
            c.write(byteBuffer);
        }
    }
}
